package com.idanchuang.support.etcache.remote.redis;

import com.idanchuang.support.etcache.remote.AbstractRemoteCache;
import com.idanchuang.support.etcache.remote.RemoteCacheConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 基于 Spring Data Redis 的 RemoteCache 实现
 *
 * @author yjy
 */
public class SpringRedisEtCache<K, V> extends AbstractRemoteCache<K, V> {

    private static final Logger logger = LoggerFactory.getLogger(SpringRedisEtCache.class);

    private final RedisConnectionFactory connectionFactory;
    private RedisConnection connection;

    public SpringRedisEtCache(RemoteCacheConfig config, RedisConnectionFactory connectionFactory) {
        super(config);
        this.connectionFactory = connectionFactory;
        if (connectionFactory == null) {
            throw new IllegalStateException("connectionFactory is required");
        }
        this.connection = connectionFactory.getConnection();
    }

    @Override
    protected byte[] doGet(byte[] keyBytes) {
        RedisConnection con = getConnection();
        return con.get(keyBytes);
    }

    @Override
    public Map<byte[], byte[]> doGetAll(List<byte[]> keyBytesList) {
        Map<byte[], byte[]> resultMap = new HashMap<>();
        RedisConnection con = getConnection();
        // build keys
        byte[][] newKeys = keyBytesList.toArray(new byte[][]{});
        // multi get
        List<byte[]> mgetResults = con.mGet(newKeys);
        if (mgetResults == null) {
            logger.error("getAll failed, mgetResults is null");
            return resultMap;
        }
        // put to map
        for (int i = 0; i < mgetResults.size(); i++) {
            resultMap.put(keyBytesList.get(i), mgetResults.get(i));
        }
        return resultMap;
    }

    @Override
    protected void doSet(byte[] keyBytes, byte[] valueBytes) {
        RedisConnection con = getConnection();
        Boolean result = con.set(keyBytes, valueBytes);
        if (Boolean.TRUE.equals(result)) {
            logger.debug("set success");
        } else {
            logger.warn("set failed");
        }
    }

    @Override
    protected void doSet(byte[] keyBytes, byte[] valueBytes, long expire, TimeUnit timeUnit) {
        RedisConnection con = getConnection();
        Boolean result = con.pSetEx(keyBytes, timeUnit.toMillis(expire), valueBytes);
        if (Boolean.TRUE.equals(result)) {
            logger.debug("set success");
        } else {
            logger.warn("set failed");
        }
    }

    @Override
    protected void doSetAll(Map<byte[], byte[]> map) {
        RedisConnection con = getConnection();
        int failCount = 0;
        for (Map.Entry<byte[], byte[]> en : map.entrySet()) {
            Boolean result = con.set(en.getKey(), en.getValue());
            if(!Boolean.TRUE.equals(result)){
                failCount++;
            }
        }
        if (failCount > 0) {
            logger.warn("setAll part failed, failed count: {}", failCount);
        }
    }

    @Override
    protected void doSetAll(Map<byte[], byte[]> map, long expire, TimeUnit timeUnit) {
        RedisConnection con = getConnection();
        int failCount = 0;
        for (Map.Entry<byte[], byte[]> en : map.entrySet()) {
            Boolean result = con.pSetEx(en.getKey(), timeUnit.toMillis(expire), en.getValue());
            if(!Boolean.TRUE.equals(result)){
                failCount++;
            }
        }
        if (failCount > 0) {
            logger.warn("setAll part failed, failed count: {}", failCount);
        }
    }

    @Override
    protected void doRemove(byte[] keyBytes) {
        RedisConnection con = getConnection();
        Long result = con.del(keyBytes);
        if (result == null) {
            logger.error("remove failed, result is null");
        } else if (result != 1 && result != 0) {
            logger.error("remove failed, result: {}", result);
        }
    }

    @Override
    protected void doRemoveAll(List<byte[]> keyBytesList) {
        RedisConnection con = getConnection();
        byte[][] newKeys = keyBytesList.toArray(new byte[][]{});
        Long result = con.del(newKeys);
        if (result == null) {
            logger.error("removeAll failed, result is null");
        }
    }

    private RedisConnection getConnection() {
        if (this.connection.isClosed()) {
            synchronized (this.connectionFactory) {
                if (this.connection.isClosed()) {
                    this.connection = this.connectionFactory.getConnection();
                }
            }
        }
        return this.connection;
    }

    @Override
    public void destroy() {
        if (this.connection != null && !this.connection.isClosed()) {
            this.connection.close();
        }
    }
}
